package m.html.util;

import m.appserver.ServerResultStatus;
import m.common.action.IVueEcharts;
import m.common.action.IVueExcel;
import m.common.action.VueResult;
import m.common.model.util.QueryOrder;
import m.common.model.util.QueryPage;
import m.html.element.*;
import m.html.element.attr.ButtonType;
import m.html.element.attr.Size;
import m.html.element.icon.IconFilterFilled;
import m.html.element.other.DisplayItem;
import m.html.list.*;
import m.system.cache.CacheUtil;
import m.system.db.TransactionManager;
import m.system.exception.MException;
import m.system.util.*;
import manage.util.excel.ExcelObject;
import manage.util.excel.SheetObject;
import manage.util.excel.SheetRow;

import java.util.*;

public abstract class VueListPage<E extends IVueListColumn,T extends VueListPage<E,T>> extends VueResult implements IVueExcel, IVueEcharts {
    protected boolean showHead=true;
    protected boolean showTitle=true;
    protected String title;
    protected String subTitle;
    protected String describe;
    protected int height;
    protected String dataUrl;
    protected String queryData;
    protected int pageSize=20;

    protected QueryPage page;
    protected QueryOrder order;
    protected Integer lineClamp=1;
    protected boolean threading=false;
    protected Map<String,String> threadMap=new HashMap<>();
    protected boolean fnColumn=true;
    protected boolean fnExcel=false;
    protected boolean fnChart=false;
    protected Integer scol=0;			//单元格合并开始列
    protected Integer slength=0;		//单元格合并长度.
    protected Integer defaultSearchNum=2;
    protected Set<String> queryFields;//所有元素所需的查询列
    protected Map<String,String> params;
    protected List<ListSearch> searchMap;
    protected List<IListElement> buttons;
    protected List<IListElement> bottomButtons;
    protected String actionName;
    private String columnsParamKey;
    protected List<String[]> columnsParam;
    private String tabField;
    private List<DisplayItem> tabItems;
    protected boolean isShowCol(IVueListColumn col){
        if(null==columnsParam) return true;
        for(String[] arr : columnsParam){
            if(ArrayUtil.isEmpty(col.getGroupTitle())){
                if(arr[0].equals(col.getTitle())) return arr[1].equals("Y");
            }else{
                if(arr[0].equals(col.getGroupTitle()[0])) return arr[1].equals("Y");
            }
        }
        return true;
    }
    protected List<JSONMessage> resetColumns(List<String> ls){
        List<JSONMessage> cs=new ArrayList<>();
        List<Integer> removeLs=new ArrayList<>();
        for(int i=0;i<ls.size();i++){
            String tmp=ls.get(i);
            String title=tmp.substring(10,tmp.indexOf("\",\""));
            if(null!=columnsParam&& !columnsParam.isEmpty()){
                String flag="Y";
                for(String[] arr : columnsParam){
                    if(tmp.startsWith("{\"title\":\""+arr[0]+"\"")&&"N".equals(arr[1])){
                        removeLs.add(i);
                        flag="N";
                        break;
                    }
                }
                cs.add(new JSONMessage().push("title",title).push("show",flag));
            }else{
                cs.add(new JSONMessage().push("title",title).push("show","Y"));
            }
        }
        for(int i=removeLs.size()-1;i>=0;i--){
            ls.remove(removeLs.get(i).intValue());
        }
        return cs;
    }

    public void setColumnsParam(String columnsParam) {
        if(StringUtil.isSpace(columnsParam)) return;
        String[] cp=columnsParam.split(",");
        List<String[]> ls=new ArrayList<>();
        for(String p : cp){
            if(StringUtil.notSpace(p))
                ls.add(p.split("\\|"));
        }
        this.columnsParam = ls;
    }

    public String getColumnsParamKey() {
        return columnsParamKey;
    }

    public void setColumnsParamKey(String columnsParamKey) {
        this.columnsParamKey = columnsParamKey;
    }

    public VueListPage<E,T> setCustomTabs(String field,List<DisplayItem> disItems){
        this.addSearch(ListSearch.custom("",field,SearchType.HIDDEN_EQ));
        this.tabField=field;
        this.tabItems=disItems;
        return this;
    }
    public VueListPage<E,T> setFieldTabs(String field,List<DisplayItem> disItems){
        this.addSearch(ListSearch.field("",field,SearchType.HIDDEN_EQ));
        this.tabField=field;
        this.tabItems=disItems;
        return this;
    }
    /**
     * 添加表格按钮
     *
     */
    public VueListPage<E,T> addButton(IListElement... buttons) {
        for(IListElement button : buttons) {
            this.buttons.add(button);
            if(button instanceof ListButton) {
                ((ListButton)button).setSize(Size.DEFAULT);
            }
            if(button instanceof ListDropdown) {
                ((ListDropdown)button).setSize(Size.DEFAULT);
            }
            this.queryFields.addAll(button.getParamFields());
        }
        return this;
    }
    /**
     * 添加表格底部按钮
     */
    public VueListPage<E,T> addBottomButton(IListElement... bottomButtons) {
        for(IListElement button : bottomButtons) {
            this.bottomButtons.add(button);
            if(button instanceof ListButton) {
                ((ListButton)button).setSize(Size.DEFAULT);
            }
            if(button instanceof ListDropdown) {
                ((ListDropdown)button).setSize(Size.DEFAULT);
            }
            this.queryFields.addAll(button.getParamFields());
        }
        return this;
    }
    /**
     * 添加查询字段, 可用于自定义属性使用
     *  queryFields
     *
     */
    public VueListPage<E,T> addQueryFields(String... queryFields) {
        this.queryFields.addAll(Arrays.asList(queryFields));
        return this;
    }
    /**
     * 添加查询元素
     *
     */
    public VueListPage<E,T> addSearch(ListSearch searchItem) {
        this.searchMap.add(searchItem);
        return this;
    }
    /**
     * 默认查询元素数量
     */
    public VueListPage<E,T> setDefaultSearchNum(Integer defaultSearchNum) {
        this.defaultSearchNum = defaultSearchNum;
        return this;
    }
    public VueListPage<E,T> hideHead(){
        this.showHead=false;
        return this;
    }
    public VueListPage<E,T> hideTitle(){
        this.showTitle=false;
        return this;
    }
    public VueListPage<E,T> setLineClamp(Integer lineClamp) {
        this.lineClamp = lineClamp;
        return this;
    }
    public VueListPage<E,T> setSubTitle(String subTitle) {
        this.subTitle = subTitle;
        return this;
    }
    public VueListPage<E,T> setDescribe(String describe) {
        this.describe=describe;
        return this;
    }

    public VueListPage<E,T> setPageSize(int pageSize) {
        this.pageSize = pageSize;
        return this;
    }
    public VueListPage<E,T> setMergeCellsInfo(Integer scol,Integer slength){
        this.scol=scol;
        this.slength=slength;
        return this;
    }
    public VueListPage<E,T> setThreading(boolean threading){
        this.threading=threading;
        return this;
    }

    public VueListPage<E,T> setFunColumn(boolean fnColumn){
        this.fnColumn=fnColumn;
        return this;
    }
    public VueListPage<E,T> setFunction(boolean fnExcel, boolean fnChart){
        this.fnExcel=fnExcel;
        this.fnChart=fnChart;
        return this;
    }
    public VueListPage<E,T> setHeight(int height) {
        this.height = height;
        return this;
    }
    public VueListPage<E,T> setPage(QueryPage page) {
        this.page = page;
        return this;
    }
    public VueListPage<E,T> setOrder(QueryOrder order) {
        this.order = order;
        return this;
    }
    protected abstract List<E> getListCols();
    protected void fillSubcount(List<?> list,String subfield,List<?> subtotal) throws MException {

    }
    protected abstract JSONMessage getQueryResult(String type) throws Exception;
    public ExcelObject toExcel() throws Exception {
        if(this.threading){//线程模式执行
            if(null==threadMap.get("excel")) {
                String threadKey = "threadKey_" + GenerateID.generatePrimaryKey();
                new Thread(() -> {
                    try {
                        CacheUtil.pushLocal(threadKey, convertExcel());
                    } catch (Exception e) {
                        CacheUtil.pushLocal(threadKey, new JSONMessage(1, e.getMessage()));
                    } finally {
                        TransactionManager.closeConnection();
                    }
                }).start();
                threadMap.put("excel",threadKey);
            }
            throw new Exception(threadMap.get("excel"));
        }else {
            return convertExcel();
        }
    }
    private ExcelObject convertExcel() throws Exception{
        boolean count=StringUtil.noSpace(params.get("excel_count")).equals("true");
        boolean merge=StringUtil.noSpace(params.get("excel_merge")).equals("true");
        this.page=null;
        JSONMessage result=getQueryResult("list"+(count?",count":""));
        List<?> list=(List<?>) result.get("data");
        List<?> summary=count?(List<?>) result.get("summary"):new ArrayList<>();
        if(null!=result.get("subfield")) {
            String subfield = (String) result.get("subfield");
            List<?> subtotal = (List<?>) result.get("subtotal");
            fillSubcount(list, subfield, subtotal);
        }
        ExcelObject excel=new ExcelObject(this.title);
        List<SheetRow> headRow=ExcelUtil.toHeadCell(this,getListCols());
        List<SheetRow> bodyRow=ExcelUtil.toBodyCell(this,getListCols(), list,summary);
        int sc=this.scol;
        int sl=this.slength;
        for(int i=0;i<getListCols().size();i++) {
            if(!isShowCol(getListCols().get(i))) {
                if(this.scol>i) sc--;
                else if(this.scol+this.slength>i) sl--;
            }
        }
        List<Integer[]> mergeCells=ExcelUtil.toHeadMergeCells(headRow);
        if(merge) mergeCells.addAll(ExcelUtil.toBodyMergeCells(headRow.size(), bodyRow, !summary.isEmpty() ?1:0, sc, sl));
        List<SheetRow> rows= new ArrayList<>();
        rows.addAll(headRow);
        rows.addAll(bodyRow);
        SheetObject sheet=new SheetObject(this.title,rows.toArray(new SheetRow[] {})).setMergeCells(mergeCells);
        excel.addSheet(sheet);

        return excel;
    }
    @Override
    public JSONMessage toEchartsOption(boolean simple) throws Exception {
        if (this.threading) {//线程模式执行
            if(null==threadMap.get("chart")) {
                String threadKey = "threadKey_" + GenerateID.generatePrimaryKey();
                new Thread(() -> {
                    try {
                        CacheUtil.pushLocal(threadKey, convertEchartsOption(simple));
                    } catch (Exception e) {
                        CacheUtil.pushLocal(threadKey, new JSONMessage(1, e.getMessage()));
                    } finally {
                        TransactionManager.closeConnection();
                    }
                }).start();
                threadMap.put("chart",threadKey);
            }
            return new JSONMessage(0).push("threadKey", threadMap.get("chart"));
        } else {
            return convertEchartsOption(simple);
        }
    }
    public JSONMessage convertEchartsOption(boolean simple) throws Exception {
        this.page=null;
        JSONMessage result=getQueryResult("list");
        List<?> list=(List<?>) result.get("data");
        return EchartsUtil.toEchartsOption(this.title,getListCols(),list,
                new Object[]{this.params.get("echarts_x"),this.params.get("echarts_y1"),this.params.get("echarts_y2")},
                simple
        );
    }

    protected abstract Element getTableElement();
    protected abstract String getVueDataPart();
    protected abstract String getVueMethodPart();

    /**
     * 获取线程执行结果
     */
    public static Object getThreadResult(String threadKey){
        Object obj=CacheUtil.get(threadKey);
        if(null!=obj) CacheUtil.clear(threadKey);
        return obj;
    }
    @Override
    public String outHtml() {
        if(!StringUtil.isSpace(this.queryData)) {
            try {
                if(this.threading){//线程模式执行
                    if(null==threadMap.get(this.queryData)) {
                        String threadKey = "threadKey_" + GenerateID.generatePrimaryKey();
                        new Thread(() -> {
                            try {
                                CacheUtil.pushLocal(threadKey, getQueryResult(this.queryData));
                            } catch (Exception e) {
                                e.printStackTrace();
                                CacheUtil.pushLocal(threadKey, new JSONMessage(1, e.getMessage()));
                            } finally {
                                TransactionManager.closeConnection();
                            }
                        }).start();
                        threadMap.put(this.queryData,threadKey);
                    }
                    return new JSONMessage(0).push("threadKey",threadMap.get(this.queryData)).toJSONString();
                }else {
                    return ObjectUtil.toString(getQueryResult(this.queryData));
                }
            } catch (Exception e) {
                e.printStackTrace();
                return new JSONMessage(1,e.getMessage()).setStatus(ServerResultStatus.ERROR).toJSONString();
            }
        }
        Element searchPanel=VueListUtil.toSearchPanel(this.searchMap,this.defaultSearchNum);
        String order="{}";
        StringBuilder colTemplate=new StringBuilder();
        for(IVueListColumn col : getListCols()) {
            if(ListSortOrder.NONE!=col.getSortOrder()) {
                order="{name:\""+col.getField()+"\",oper:\""+col.getSortOrder().getOrder()+"\"}";
            }
            colTemplate.append(col.toExtendString(this.actionName,this.lineClamp));
        }
        Element tabs = null;
        if(null!=this.tabItems&& !this.tabItems.isEmpty()){
            tabs=Element.tag("a-radio-group").A("v-model:value","inputParams['"+this.tabField+"']").A("button-style","solid")
                    .A("@change","queryData(1);");
            for(DisplayItem di : this.tabItems) {
                tabs.E(Element.tag("a-radio-button").A("value",di.getValue()).E(di.getTitle()));
            }
        }
        Element div=Element.tag("div")
        .E(
            this.showHead?Element.tag("a-page-header")
            .A(":back-icon","false").A("@back","back(false)").A("title",this.showTitle?this.title:"")
            .E(
                Element.tag("template").A("#subTitle").E(tabs).E(new Text(this.subTitle))
            )
            .E(
                Element.tag("template").A("#extra")
                .E(this.buttons)
                .E(
                    Element.tag("m-back").E(new Text("{{param.openType=='PAGE'?'返回':'关闭'}}")).setExtend(" v-if=\"param.isOpen\" @click=\"back(false);\" ")
                )
            ):null
        )
        .E(
            Element.tag("div").A("style", "padding:0 15px;").E(new Text(this.describe))
        ).E(searchPanel)
        .E(
            Element.tag("a-card").A("hoverable").A(":bodyStyle", "{padding:0}").A("style","margin:0 15px 15px;")
            .E(
                getTableElement().A("class","index-table")
                .E(new Text(colTemplate.toString()))
                .E(
                    Element.tag("template").A("#emptyText").E(
                        Element.tag("div").A("style","padding:29px 0;")
                        .E(
                            Element.tag("").E("<svg width=\"64\" height=\"41\" viewBox=\"0 0 64 41\" xmlns=\"http://www.w3.org/2000/svg\"><g transform=\"translate(0 1)\" fill=\"none\" fill-rule=\"evenodd\"><ellipse fill=\"#f5f5f5\" cx=\"32\" cy=\"33\" rx=\"32\" ry=\"7\"></ellipse><g fill-rule=\"nonzero\" stroke=\"#d9d9d9\"><path d=\"M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z\"></path><path d=\"M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z\" fill=\"#fafafa\"></path></g></g></svg>"),
                            Element.tag("div").E("{{tableLoad?'数据查询中...请稍后':'暂无数据'}}")
                        )
                    )
                )
                .E(
                    Element.tag("template").A("#filter-icon","filtered")
                    .E(
                        Element.tag("div").A("style","display:flex;align-items:center;justify-content:center;")
                        .E(new IconFilterFilled().setSize(12).setColor("var(--colorPrimary)").A("v-if","isHasSearch(filtered.column.key)"))
                        .E(new IconFilterFilled().setSize(12).A("v-else"))
                    )
                )
                .E(
                    Element.tag("template").A("#summary")
                    .E(
                        Element.tag("a-table-summary").A("fixed").A("v-if", "tableSummary.length>0&&tableData.length>0")
                        .E(
                            Element.tag("a-table-summary-row")
                            .E(
                                Element.tag("a-table-summary-cell").A("v-for", "(item,index) in tableSummary").A(":index","index")
                                .E(
                                    Element.tag("div").A("class","td-wrap").A("style","-webkit-line-clamp:1;").A(":style", "{textAlign:item[2],color:(paginationLoad?'#9999':'')}")
                                    .A(":title","formatter('',item[0],item[1])").E(new Text("{{formatter('',item[0],item[1])}}"))
                                )
                            )
                        )
                    )
                )
            )
            .E(
                Element.tag("a-flex").A("justify","space-between").A("gap","small").A("style","padding:8px;").A("class","bg-color")
                .E(
                    Element.tag("a-flex").A("gap","small")
                    .E(
                        Element.tag("a-popover").E(
                            Element.tag("template").A("#content").E(new Text("上次查询耗时{{queryLong}}秒<div v-if=\"countLong\">上次汇总耗时{{countLong}}秒</div>")),
                            new Button(ButtonType.PRIMARY).E(new Icon("icon-icon-72")).setExtend(" @click=\"queryData();\" ")
                        ),
                        fnColumn?Element.tag("a-popover").A("v-model:open","showView").A("@openChange","hideResetInputParams")
                            .E(
                                new Button().E(new Icon("icon-column")).setExtend(" @click=\"showView=true;\" "),
                                Element.tag("template").A("#content").E(VueListUtil.toViewColumnElement(getListCols(),this.params))
                            ):null,
                        fnExcel?Element.tag("a-popover").E(
                            Element.tag("template").A("#content")
                            .E(Element.tag("div").A("v-if","downExcel").E("正在导出Excel"))
                            .E(Element.tag("div").A("v-else")
                                .E(Element.tag("div").E(new CheckBox("inputParams['excel_count']").E("合计行")))
                                .E(Element.tag("div").E(new CheckBox("inputParams['excel_merge']").E("列合并")).A("v-if","slength>0"))
                                .E(Element.tag("div").E("↓点击导出Excel"))
                            ),
                            new Button().A(":disabled","downExcel").E(new Icon("icon-icon-126")).setExtend(" @click=\"excelData();\" ")
                        ):null,
                        fnChart?Element.tag("a-popover").E(
                            Element.tag("template").A("#content").E(EchartsUtil.toPopoverContent(getListCols())),
                            new Button().E(new Icon("icon-icon-134")).setExtend(" ")
                        ):null,
                        Element.tag("a-spin").A(":spinning","paginationLoad").A("size","small")
                        .E(null==page?Element.tag("div").A("style","line-height:32px;height:32px;").E(new Text("共{{tableCount}}条")):
                            Element.tag("a-pagination").A("v-model:current","page.index").A("v-model:page-size","page.num")
                            .A(":total","tableCount").A("show-less-items").A("@change","queryData").A("show-size-changer")
                            .A("@show-size-change","queryData").A(":disabled","paginationLoad||tableLoad&&page.index<=1")
                            .A(":show-total","(total, range) => '共'+total+'条'")
                            .A(":page-size-options","["+VueListUtil.getPageSizes(this.pageSize)+"]")
                            .E(new Text("<template #buildOptionText=\"props\"><span v-if=\"props.value !== '9999999999'\">{{ props.value }}条/页</span><span v-else>全部</span></template>"))
                        )
                    )
                )
                .E(Element.tag("a-flex").A("gap","small").E(this.bottomButtons))
            )
        );

        StringBuilder sb=new StringBuilder();
        sb.append("{")
        .append(" template:`").append(div).append("`,")
        .append(" props: ['localeZh','param','parentVue'],")
        .append(" data(){")
        .append("  return{")
        .append("   showSearch:false,showView:false,queryLong:'',countLong:'',downExcel:false,");
        String ps= "{}";
        try {
            if(null!=this.params) ps=ObjectUtil.toString(this.params);
        }catch(Exception ignored) {}
        sb.append("   searchParams:{},");
        sb.append("   inputParams:Object.assign(").append(ps).append(",{");
        for(ListSearch ls : this.searchMap)  sb.append(ls.outField());
        sb.append("}),")
        .append("   dataUrl:\"").append(this.dataUrl).append("\",")
        .append("   columnsSetting:{},")
        .append("   columnsParamKey:'").append(getColumnsParamKey()).append("',")
        .append("   columnsSort:[],")
        .append("	columns:[],")
        .append("   colWidth:{},")
        .append("   asMap:{},")
        .append("	tableLoad:false,")
        .append("	paginationLoad:false,")
        .append("	pagination:{},")
        .append("   tableData:[],")
        .append("	tableSummary:[],")
        .append("   tableSubtotal:[],")
        .append("   tableSubfield:'',")
        .append("   tableCount:0,")
        .append("	width:0,")
        .append("	bodyHeight:").append(this.height<=0?"300":this.height-100).append(",")
        .append("	height:").append(this.height==0?"null":this.height).append(",")
        .append("   page:{index:").append(null!=page?page.getIndex():"1").append(",num:").append(VueListUtil.getPageNum(this.page,this.pageSize)).append("},")
        .append("   order:").append(order).append(",")
        .append("   scol:").append(this.scol).append(",")
        .append("   slength:").append(this.slength).append(",")
        .append(getVueDataPart())
        .append("  };")//return {};
        .append(" },")
        .append(" mounted(){");
        if(null!=this.params) {
            for(ListSearch ls : this.searchMap) {
                if(!ls.getDisItems().isEmpty() ||!StringUtil.isSpace(ls.getDictType())) {
                    String str=StringUtil.noSpace(this.params.get(ls.getField()));
                    if(str.matches("^\\[.*\\]$")) {}
                    else if(!StringUtil.isSpace(str)) str= ArrayUtil.toString(str.split(","));
                    else str="[]";
                    sb.append(" this.inputParams['").append(ls.getField()).append("']=").append(str).append(";");
                }else if(ls.getType().toString().contains("RANGE")) {
                    sb.append(" this.inputParams['").append(ls.getField()).append("_down']=\"").append(StringUtil.noSpace(this.params.get(ls.getField()+"_down"))).append("\";");
                    sb.append(" this.inputParams['").append(ls.getField()).append("_up']=\"").append(StringUtil.noSpace(this.params.get(ls.getField()+"_up"))).append("\";");
                }else {
                    sb.append(" this.inputParams['").append(ls.getField()).append("']=\"").append(StringUtil.noSpace(this.params.get(ls.getField()))).append("\";");
                }
            }
        }
        sb.append("  this.queryData(1);")
        .append(" },")
        .append(" methods:Object.assign(mo.listResult.methods,{")
        .append(getVueMethodPart())
        .append(" })")
        .append("}");
        return sb.toString();
    }
}
